<?php
if (!defined( "PATH" )) {
    define( "PATH", dirname(__FILE__).'/../');
}

$a = new AssemblyBuilder();
$a->run($argv, $argc);
    
class AssemblyBuilder
{
    private static $_paths = array(PATH);
    private static $_skipFolders = array('tmp', '.svn', 'sqls', 'logs', 'project', 'simpletest', 'Zend');
    private static $_fileNameTemplate = array('php');
    private static $_assemblyFileName = 'assembly.php';

    private $_isClearOldCache;
    private $_cacheKeyName;
    private $_clearOldKeyName;
    
    public function run($argv, $argc)
    {/*{{{*/
        $this->parseParams($argv, $argc);
        foreach (self::$_paths as $path)
        {
            $files = self::findFiles($path);
            foreach (self::findClasses($files) as $class => $filename)
            {
                if (empty($classes[$class]))
                    $classes[$class] = $filename;
                else
                    echo "Repeatedly Class $class in file $filename\n";
            }
        }
        
        $this->generatorAssemblyFile($classes);

        self::genIncludeMap($classes);

        $this->clearOldCache();
    
        echo "\ngenerator assembly file successed!\n";
    }/*}}}*/

    private function clearOldCache()
    {/*{{{*/
        if ($this->_isClearOldCache)
        {
            if (function_exists('apc_delete'))
            {
                apc_delete($this->_clearOldKeyName);
            }
            else if (function_exists('eaccelerator_rm'))
            {
                eaccelerator_rm($this->_clearOldKeyName);
            }
        }
    }/*}}}*/

    private function parseParams($argv, $argc)
    {/*{{{*/
        $p1 = isset($argv[1])?$argv[1]:'';

        $cacheKeyName = isset($argv[2])?$argv[2]:'';
        $this->_isClearOldCache = isset($argv[3])?true:false;
        $clearOldKeyName = isset($argv[4])?$argv[4]:'';

        if ('-h' == $p1 || '-help' == $p1 || 1 == $argc)
        {
            $this->showHelp();
        }
        if ( ('-path' == $p1) || ('-p' == $p1) )
        {
            if ('' == $cacheKeyName)
            {
                $this->showError('no path given!');
            }
            $this->_cacheKeyName = $cacheKeyName;
        }
        if ('' != $clearOldKeyName)
        {
            $this->_clearOldKeyName = $clearOldKeyName;
        }
        else
        {
            $this->_clearOldKeyName = $cacheKeyName;
        }
    }/*}}}*/

    private function showHelp()
    {/*{{{*/
        echo "\ne.g. php buildincludes.php -p qucclasspath -c \n
-p: the cache key name for ea or apc\n
-c: whether clear the old one or not, when not given -c oldname, the -p newname will be tried.\n\n";
        exit;
    }/*}}}*/

    private function showError($msg)
    {/*{{{*/
        echo "\n\n$msg\n\n";
        exit;
    }/*}}}*/

    private function generatorAssemblyFile($classes)
    {/*{{{*/
        $assemblyfile = new File(PATH."/".self::$_assemblyFileName);
        $assemblyfile->open("w+");
        $assemblyfile->write("<?php\n");
        $assemblyfile->write("    function myAutoloader(\$classname)\n");
        $assemblyfile->write("    {\n");
        $assemblyfile->write("        static \$classpath;\n");
        $assemblyfile->write("        if ('' == \$classpath)\n");
        $assemblyfile->write("        {\n");
        $assemblyfile->write("            if (function_exists('eaccelerator_get'))\n");
        $assemblyfile->write("            {\n");
        $assemblyfile->write("                \$classpath = eaccelerator_get('$this->_cacheKeyName');\n");
        $assemblyfile->write("                if (false == \$classpath)\n");
        $assemblyfile->write("                {\n");
        $assemblyfile->write("                    include('map.php');\n");
        $assemblyfile->write("                    eaccelerator_put('$this->_cacheKeyName', \$classpath);\n");
        $assemblyfile->write("                }\n");
        $assemblyfile->write("            }\n");
        $assemblyfile->write("            else if(function_exists('apc_fetch'))\n");
        $assemblyfile->write("            {\n");
        $assemblyfile->write("                \$classpath = apc_fetch('$this->_cacheKeyName');\n");
        $assemblyfile->write("                if (false == \$classpath)\n");
        $assemblyfile->write("                {\n");
        $assemblyfile->write("                    include('map.php');\n");
        $assemblyfile->write("                    apc_store('$this->_cacheKeyName', \$classpath);\n");
        $assemblyfile->write("                }\n");
        $assemblyfile->write("            }else{\n");
        $assemblyfile->write("                    include('map.php');\n");
        $assemblyfile->write("            }\n");
        $assemblyfile->write("        }\n");
        $assemblyfile->write("        if (!empty(\$classpath[\$classname]))\n");
        $assemblyfile->write("        {\n");
        $assemblyfile->write("            include(\$classpath[\$classname]);\n");
        $assemblyfile->write("        }\n");
        $assemblyfile->write("    }\n");
        $assemblyfile->close();
    }/*}}}*/

    static private function genIncludeMap($classes)
    {/*{{{*/
        $assemblyfile = new File(PATH."/map.php");
        $assemblyfile->open("w+");
        $assemblyfile->write("<?php\n");
        $assemblyfile->write("        static \$classpath = array(\n");
        foreach ($classes as $key => $value)
        {
            $assemblyfile->write("			\"$key\" => \"$value\",\n");
        }
        $assemblyfile->write("        );\n");
        $assemblyfile->close();
    }/*}}}*/
    
    static private function findClasses($files)
    {/*{{{*/
        $classes = array();
	    foreach ($files as $file)
	    {
	        foreach (self::findClassFromAFile($file) as $class)
	        {
	            if (empty($classes[$class]))
	                $classes[$class] = $file;
	            else
                    echo "Repeatedly Class $class in file $file\n";
	        }
	    }
        //var_dump($classes);
        //exit;
	    return $classes;
    }/*}}}*/
    static private function findClassFromAFile($file)
    {/*{{{*/
        $classes = array();
        $lines = file($file);
        foreach ($lines as $line)
        {
            if (preg_match("/^\s*class\s+(\S+)\s*/", $line, $match))
            {
                $classes[] = $match[1];
            }
            if (preg_match("/^\s*abstract\s*class\s+(\S+)\s*/", $line, $match))
            {
                $classes[] = $match[1];
            }
            if (preg_match("/^\s*interface\s+(\S+)\s*/", $line, $match))
            {
                $classes[] = $match[1];
            }

        }
        return $classes;
    }/*}}}*/

    static private function skipFiles($file)
    {/*{{{*/
        $suffix = self::getFileSuffix($file);
        return ( false == in_array($suffix, self::$_fileNameTemplate) 
//            ||  (true == is_link($file)) 
            || (1 == preg_match("/\.svn/", $file)) || (0 == preg_match("/.+\.php/", $file)) ); 
    }/*}}}*/

    static private function isInFileTemplates($file)
    {
    }

    static private function isSkipFolders($file)
    {/*{{{*/
        if (is_link($file))
        {
            return true;
        }

        foreach (self::$_skipFolders as $skip)
        {
            $skip = quotemeta($skip);
            //echo $file;
            //$file = '/usr/home/ysq/svn/quc/src//web-inf/mu/mystorage/.svn/wcprops/test.php.svn-work';
            //var_dump(preg_match("/$skip/", $file));
            //exit;
            if (1 == preg_match("/$skip/", $file))
            {
                return true;
            }
        }
    }/*}}}*/
    
    static private function findFiles($dirname)
    {/*{{{*/
         $filelist = array();
         $currentfilelist = scandir($dirname);
         foreach ($currentfilelist as $file)
         {
             if ($file == "." || $file == ".." || self::isSkipFolders($file))
             {
                 continue;
             }

             $file = "$dirname/$file";

             if (is_dir($file))
             {
                 foreach (self::findFiles($file) as $tmpFile)
                 {
                     $filelist[] = $tmpFile;
                 }
                 continue;
             }

             if (false == self::skipFiles($file))
             {
                echo $file."\n";
                $filelist[] = $file;
             }
         }
         return $filelist;
    }/*}}}*/

    static private function getFileSuffix($fileName)
    {/*{{{*/
        $pointPos = strrpos($fileName, "."); 
         
        if ($pointPos)
        {
            return substr($fileName, $pointPos+1, strlen($fileName) - $pointPos); 
        }
        return;
    }/*}}}*/
}

class File
{/*{{{*/

    var $_fileName;
    var $_handler;
    var $_mode;

    function File( $fileName )
    {
        $this->_fileName = $fileName;
    }

    function open( $mode = "r" )
    {
        $this->_handler = fopen( $this->_fileName, $mode );

        $this->_mode = $mode;

        return $this->_handler;
    }

    function close()
    {
        fclose( $this->_handler );
    }

    function readFile()
    {
        $contents = Array();

        $contents = file( $this->_fileName );

        for( $i = 0; $i < count( $contents ); $i++ )
            $contents[$i] = trim( $contents[$i] );

        return $contents;
    }

    function read( $size = 4096 )
    {
        return( fread( $this->_handler, $size ));
    }

    function eof()
    {
        return feof( $this->_handler );
    }

    function write( $data )
    {
        return fwrite( $this->_handler, $data );
    }

    function truncate( $length = 0 )
    {
        return ftruncate( $this->_handler, $length );
    }

    function writeLines( $lines )
    {
        // truncate the file to remove the old contents
        $this->truncate();

        foreach( $lines as $line ) {
            //print("read: \"".htmlentities($line)."\"<br/>");
            if( !$this->write( $line, strlen($line))) {
                return false;
            }
             /*else
             print("written: \"".htmlentities($line)."\"<br/>");*/
        }

        return true;
    }

    function isDir( $file = null )
    {
        if( $file == null )
            $file = $this->_fileName;

        return is_dir( $file );
    }

    function isWritable( $file = null )
    {
        if( $file == null )
            $file = $this->_fileName;

        return is_writable( $file );
    }

    function isReadable( $file = null )
    {
        if( $file == null )
            $file = $this->_fileName;

        clearstatcache();

        return is_readable( $file );
    }
}/*}}}*/
